// 第五步：实现要收集的依赖 Watcher 类 (相当于订阅者)
// 1.模板{{ }}、模板中属性绑定的值、computed等中使用到数据的地方都称为依赖
// 2.找到依赖的时候把依赖放到一个全局的位置 window.target，依赖被收集之后就清空当前的值，设置为 undefined
export class Watcher {
  constructor(vm, expOrFn, cb) {
    // 1.被订阅的数据来源：来自于当前 vue 的实例
    this.vm = vm
    // 2.绑定 doGet 方法，在调用 this.doGet 的时候就去读取 data 中的数据 例 expOrFn = 'a.b.c' => 就去读 data.a.b.c 的内容
    this.doGet = parsePath(expOrFn)
    // 3.数据更新时执行的回调
    this.cb = cb
    // 4.维护更新之前的数据
    this.value = this.get()
  }

  get() {
    // 1.先把 window.target 设置为 this(当前的 watcher 实例)，然后再去读取 data.a.b.c 的值，
    // 就会通知订阅管理者收集此依赖，把 this 添加到 data.a.b.c 对应 Dep 中
    window.target = this
    // 2.获取 value 的值，第一个 this.vm 作为 parsePath() 的调用者的 this，第二个作为 parsePath() 返回的方法的参数 obj
    let value = this.doGet.call(this.vm, this.vm)
    // 3.收集完此依赖，就把全局的 window.target 置空，用于下一个 watcher 收集
    window.target = undefined
    // 4.get 返回 调用 doGet 获取到的值
    return value
  }

  // 订阅管理者中触发的 watcher 的变更逻辑
  update() {
    // 保存旧值，设置新值，并且把当前 watcher 实例、新值和旧值作为回调的参数
    const oldValue = this.value
    this.value = this.get()
    console.log('旧值：' + oldValue)
    console.log('新值：' + this.value)
    this.cb.call(this.vm, this.value, oldValue)

    // 更新回显
    let result = document.querySelector('.result')
    result.innerText = this.value
  }
}

/** 解析简单路径 */
function parsePath(path) {
  const regExp = /[^\w.$]/ // 正则匹配 非数字、字母、下划线、点和$
  if (regExp.test(path)) return
  // 使用.分割 path 转为数组
  const arr = path.split('.')
  return function (obj) {
    for(let i = 0; i < arr.length; i++) {
      if (!obj) return
      // 循环一级一级往下去，返回最后拿到的值
      obj = obj[arr[i]]
    }
    return obj
  }
}
